package com.bj58.mis.apm.server.kylinreceive.service.impl;

import com.bj58.mis.apm.server.core.util.MD5;
import com.bj58.mis.apm.server.core.util.WOSScheduledService;
import com.bj58.mis.apm.server.kylinreceive.dto.KyLog;
import com.bj58.mis.apm.server.kylinreceive.dto.KyLogList;
import com.bj58.mis.apm.server.kylinreceive.mapper.KylinLogMapper;
import com.bj58.mis.apm.server.kylinreceive.model.KylinLog;
import com.bj58.mis.apm.server.kylinreceive.model.KylinQueryLogOutput;
import com.bj58.mis.apm.server.kylinreceive.repository.SearchRepository;
import com.bj58.mis.apm.server.kylinreceive.service.KylinSuggestService;
import com.bj58.mis.apm.server.kylinreceive.service.KylogService;
import com.bj58.mis.apm.server.kylinreceive.service.WarnService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Configuration
public class KylogServiceImpl implements KylogService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private Map<String, String> projectMap = new HashMap<>();

    private Map<String, String> cubeMap = new HashMap<>();

    private Map<String, String> segmentMap = new HashMap<>();

    @Resource
    private SearchRepository searchRepository;
    @Resource
    private KylinSuggestService suggestService;

    @Resource
    private KylinLogMapper logMapper;

    @Autowired
    private WOSScheduledService wosScheduledService;

    @Autowired
    private WarnService warnService;

    @Value("${querylog.output.saveas}")
    private String outputSaveAs;

    @Value("${kylin.query.log.sql.contain}")
    private String contain;
    @Value("${kylin.query.log.sql.notcontain}")
    private String notContain;

    /**
     * 日志接收处理
     * 如果log对应多个cube，需要将lob按照cube进行进行分为多条记录进行保存
     * 按照cubeid和exec sql生成的MD5作为该记录的分组标识
     * kylinlog的id为生成UUID
     * 根据projectName、cubeName、segmentName获取projectId、cubeId、segmentId
     * @param list
     */
    @Override
    public void receive(KyLogList list) {
        logger.debug("receive log size = " + list.getLogList().size());
        List<KylinLog> logTempList = new ArrayList<>();
        List<KylinQueryLogOutput> outputTempList = new ArrayList<>();

        for(KyLog log : list.getLogList()){
            if(!checkSqlContain(log.getSql())){
                continue;
            }
            try {
                KylinLog kylinLog = new KylinLog();
                BeanUtils.copyProperties(log, kylinLog);

                dealFields(kylinLog);
                kylinLog.setExecSql(log.getSql());
                kylinLog.setSuccess(Boolean.parseBoolean(log.getSuccess()));
                kylinLog.setDuration(Double.parseDouble(log.getDuration()));
                kylinLog.setHost(list.getHostName());

                if (StringUtils.isNotEmpty(log.getProject())) {
                    String projectId = getProjectIdByName(log.getProject());
                    if (projectId != null) {
                        kylinLog.setProjectId(projectId);
                    }
                }

                List<String> realizationArray = splitAndDistinct(log.getRealizationNames(), ",");
                List<String> cuboidArray = splitAndDistinct(log.getCuboidIds(), ",");
                if(realizationArray.size() > 0){
                    for(int i = 0; i < realizationArray.size() || i < cuboidArray.size(); i++){

                        KylinLog newLog = kylinLog.clone();

                        String str = i < realizationArray.size() ? realizationArray.get(i) : realizationArray.get(realizationArray.size() - 1);
                        String cubeName = str.substring(str.indexOf("CUBE[name=") + 10, str.lastIndexOf("]"));
                        String cubeId = getCubeId(cubeName, newLog.getProjectId());
                        newLog.setCubeId(cubeId);

                        if(cuboidArray.size() > 0){
                            String cuboid = i < cuboidArray.size() ? cuboidArray.get(i) : cuboidArray.get(cuboidArray.size() - 1);
                            newLog.setCuboid(Long.parseLong(cuboid));
                        }

                        /*
                        if (StringUtils.isNotEmpty(cubeId)) {
                            String segmentName = log.getSegment();
                            if (segmentName == null || segmentName.equals("")) {
                                segmentName = "FULL_BUILD";
                            }
                            String segmentId = getSegmentIdByName(segmentName, cubeId);
                            if (segmentId != null) {
                                newLog.setSegmentId(segmentId);
                            }
                        }
                        */
                        getSegmentId(newLog, log.getSegment());

                        newLog.setMd5(MD5.encode(newLog.getCubeId(), newLog.getExecSql()));
                        logTempList.add(newLog);
                    }
                }else{
                    kylinLog.setCubeId("");
                    kylinLog.setCuboid(-1L);
                    kylinLog.setMd5(MD5.encode(kylinLog.getCubeId(), kylinLog.getExecSql()));
                    logTempList.add(kylinLog);
                }

                KylinQueryLogOutput output = new KylinQueryLogOutput();
                output.setQueryId(log.getQueryId());
                output.setOutput(log.getSb());

                outputTempList.add(output);

            }catch (Exception e){
                logger.error(log.getQueryId());
                throw new RuntimeException(e);
            }

            if(logTempList.size() >= 1000){
                saveData(logTempList, outputTempList);
            }
        }
        if(logTempList.size() > 0 || outputTempList.size() > 0){
            saveData(logTempList, outputTempList);
        }
    }

    /**
     * 分隔和去重
     * @param source
     * @param splitBy
     * @return
     */
    private List<String> splitAndDistinct(String source, String splitBy){
        List<String> result = new ArrayList<>();
        if(StringUtils.isNotEmpty(source) && !"[]".equals(source)) {
            String[] splits = source.substring(1, source.length() - 1).split(splitBy);
            for (String s : splits) {
                if (result.contains(s.trim())) {
                    continue;
                }
                result.add(s.trim());
            }
        }
        return result;
    }
    /**
     * 处理grouplist和filterlist属性，去掉属于那个database
     * @param kylinLog
     */
    private void dealFields(KylinLog kylinLog){
        String groupList = kylinLog.getGroupList();
        if(StringUtils.isNotEmpty(groupList)){
            String[] groups = groupList.split(",");
            for(int i = 0 ; i < groups.length ; i++){
                String temp = groups[i];
                if(temp.indexOf(".") < temp.lastIndexOf(".")){
                    groups[i] = temp.substring(temp.indexOf(".")+1);
                }
            }
            kylinLog.setGroupList(StringUtils.join(groups, ","));
        }

        String filterList = kylinLog.getFilterList();
        if(StringUtils.isNotEmpty(filterList)){
            String[] filters = filterList.split(",");
            for(int i = 0 ; i < filters.length ; i++){
                String temp = filters[i];
                if(temp.indexOf(".") < temp.lastIndexOf(".")){
                    filters[i] = temp.substring(temp.indexOf(".")+1);
                }
            }
            kylinLog.setFilterList(StringUtils.join(filters, ","));
        }

    }

    /**
     * 批量保存数据
     * @param logList
     * @param outputList
     */
    @Transactional
    protected void saveData(List<KylinLog> logList, List<KylinQueryLogOutput> outputList){
        logMapper.saveOrUpdateQueryLog(logList);

        warnService.checkQuery(logList);

        if("WOS".equals(this.outputSaveAs.toUpperCase())){
            outputList.forEach(ot -> {
                wosScheduledService.uploadFile(ot.getQueryId(), ot.getOutput());
            });
        }else {
            if (outputList.size() > 100) {
                int i = 0;
                while (i * 100 < outputList.size()) {
                    logMapper.saveOrUpdateQueryLogOutput(outputList.subList(i * 100, Math.min(i * 100 + 100, outputList.size())));
                    i++;
                }
            } else {
                logMapper.saveOrUpdateQueryLogOutput(outputList);
            }
        }
        /**
         * 更新查询次数统计
         */
        suggestService.updateQueryTimes(logList);

        logList.clear();
        outputList.clear();
    }

    /**
     * 根据项目名称查询ID
     * @param projectName
     * @return
     */
    private String getProjectIdByName(String projectName){
        String projectId = projectMap.get(projectName);
        if(projectId == null){
            projectId = searchRepository.queryProjectIdByName(projectName);
            if(projectId != null){
                projectMap.put(projectName, projectId);
            }else{
                projectMap.put(projectName, "null");
            }
        }else if("null".equals(projectId)){
            projectId = null;
        }
        return projectId;
    }

    /**
     * 根据Cube名称查询ID
     * @param cubeName
     * @return
     */
    private String getCubeId(String cubeName, String projectId){
        String cubeId = cubeMap.get(cubeName);
        if(cubeId == null){
            cubeId = searchRepository.queryCubeIdByNameAndProjectId(cubeName, projectId);
            if(cubeId == null){
                cubeId = "";
            }
            cubeMap.put(cubeName, cubeId);
        }
        return cubeId;
    }

    /**
     * 根据采集segment内容查询响应ID
     * 但目前暂时不保存查询和segment的映射关系
     * @return
     */
    private void getSegmentId(KylinLog log, String segment){
        if(log.getCubeId() == null){
            return;
        }
        if(StringUtils.isNotEmpty(log.getCubeId()) && StringUtils.isNotEmpty(segment)){
            if(segment.indexOf(",") < 0){
                log.setSegmentId(searchRepository.querySegmentIdByName(segment, log.getCubeId()));
            }else {
                String[] segments = segment.split(",");
                List<String> segidList = new ArrayList<>();
                for (String seg : segments) {
                    String cubeName = seg.substring(0, seg.indexOf("["));
                    String segmentName = seg.substring(seg.indexOf("[") + 1, seg.indexOf("]"));
                    String segmentId = getSegmentId(cubeName, segmentName, log.getCubeId());
                    if (segmentId != null) {
                        segidList.add(segmentId);
                    }
                }
                if (segidList.size() > 0) {
                    log.setSegmentId(String.join(",", segidList));
                }
            }
        }
    }

    private String getSegmentId(String cubeName, String segmentName, String cubeId){
        if(cubeId.equals(cubeMap.get(cubeName))){
            String key = cubeId+"."+segmentName;
            String segmentId = segmentMap.get(key);
            if(segmentId == null){
                segmentId = searchRepository.querySegmentIdByName(segmentName, cubeId);
                if(segmentId != null){
                    segmentMap.put(key, segmentId);
                }else{
                    segmentMap.put(key, "null");
                }
            }else if("null".equals(segmentId)){
                segmentId = null;
            }
            return segmentId;
        }
        return null;
    }

    private boolean checkSqlContain(String sql){
        if(StringUtils.isEmpty(sql)){
            return false;
        }
        sql = sql.toLowerCase();
        if(StringUtils.isNotEmpty(contain)) {
            String[] contains = contain.split(",");
            for (String con : contains) {
                if (sql.indexOf(con.toLowerCase()) == -1) {
                    return false;
                }
            }
        }
        if(StringUtils.isNotEmpty(notContain)) {
            String[] notContains = notContain.split(",");
            for (String notcon : notContains) {
                if (sql.startsWith(notcon.toLowerCase())){
                    return false;
                }
            }
        }
        return true;
    }

}
